<?php

	/**
	 * @see Bike_Js_Abstract
	 */
	require_once 'Bike/Js/Abstract.php';
		
	
	class Bike_Js_Variable extends Bike_Js_Abstract implements ArrayAccess
	{
        const CLASS_NAME_PART_DELIM = '_';
        const CLASS_NAME_CACHE_DELIM = '.';
		
		static protected $_useCaching = True;
		
		static protected $_varsCache = array();
		
		protected $_name = array();
        protected $_fullName = array();
        protected $_varCaching = True;
        protected $_cacheNameStr;
        
        static public function getUseCaching()
        {
        	return self::$_useCaching;
        }
        
        static public function setUseCaching($caching)
        {
        	self::$_useCaching = $caching;
        }
        
        static public function getVarsCache($name=null)
        {
        	if(is_null($name))
        	{
        		return (array) self::$_varsCache;
        	}
        	elseif(isset(self::$_varsCache[$name]))
        	{
        		return self::$_varsCache[$name];
        	}
        	
        	require_once 'Bike/Js/Exception.php';
        	throw new Bike_Js_Exception(sprintf('There are no cache for "%s"', $name));
        }
        
        static protected function setVarsCache($name, $value)
        {
        	self::$_varsCache[$name] = $value;
        }
        
        public function makeOptions(array $Args=null)
		{
			$options = $Args;
			$argsCount = sizeof($Args);
			if($argsCount)
			{
				if($argsCount == 1 && is_array($Args[0]) && isset($Args[0]['name']))
				{
					$options = $Args[0];
				}
				elseif($argsCount >= 1)
				{
					$options = array(
						'name' => $Args[0],
					);
				}
			}
			return $options;
		}

		public function _toString()
		{			$Name = $this->getName();
			$_name = array();
			foreach($Name as $namePart)
			{				array_push($_name, $namePart);
			}
			$str = join('.', $_name);
			if($this->_last()->getInitObject())
			{				return (string) $this->js()->new($this->js()->Expr($str));
			}
			return $str;
		}
		
		public function setVarCaching($flag = True)
		{
			$this->_varCaching = $flag;
			return $this;
		}
		
		public function getVarCaching()
		{
			return $this->_varCaching;
		}

		protected function setName($Name)
		{			if(!is_array($Name))
			{				$Name = array($Name);
			}
			$fullNameFlag = False;
			if($this->getFullName())
			{				$fullNameFlag = True;
			}
			foreach($Name as $n => $name)
			{
				if(!$name instanceof Bike_Js_VarPart)
				{
					$Name[$n] = $this->_getNamePart((string) $name, $this->getSettingVarTypeAttr());
				}
				if(!$fullNameFlag)
				{					$this->addFullName($Name[$n]);
				}
			}
			$this->_name = $Name;
			return $this;
		}

		protected function addName(Bike_Js_VarPart $Name)
		{
			$this->addFullName($Name);
			$name = $this->getName();
			$offset = $Name->getPartOffset();
			if(is_int($offset))
			{
				$len = ($offset < 0) ? (sizeof($name) + $offset) : $offset;
				$name = array_slice($name, 0, $len);
			}
			array_push($name, $Name);
			$this->setName($name);
			return $this;
		}

		protected function addFullName(Bike_Js_VarPart $Name)
		{			array_push($this->_fullName, $Name->getAccessor());
			if($this->getVarCaching() && !$Name->getVarCaching())
			{
				$this->setVarCaching(False);
			}
			$this->setCacheNameStr();
		}
		
		protected function setCacheNameStr()
		{
			$this->_cacheNameStr = join(self::CLASS_NAME_CACHE_DELIM, $this->getFullName());
		}

		protected function getName()
		{
			return (array) $this->_name;
		}

		protected function getFullName()
		{
			return (array) $this->_fullName;
		}
		
		protected function getCacheNameStr()
		{
			return $this->_cacheNameStr;
		}
		
		protected function getSettingVarTypeAttr()
		{
			return array('varPartType' => 'Setting');
		}
		
		public function getClassNamePartStr($name = null, $parts = null, $delim = self::CLASS_NAME_PART_DELIM)
		{
			if(is_null($parts))
			{
				$parts = $this->getClassNameFromName();
			}
			if(!reset($parts))
			{
				array_shift($parts);
			}
			if(!is_null($name))
			{
				array_push($parts, $name);
			}
			return join($delim, $parts);
		}
		
		public function getClassNameCacheStr($name=null)
		{
			return $this->getClassNamePartStr($name, array($this->getCacheNameStr()), self::CLASS_NAME_CACHE_DELIM);
		}

		public function __call($methodName, array $args=null)
		{
			switch($methodName)
			{
				default:
					try
					{
						return parent::__call($methodName, $args);
					}
					catch(Bike_Exception $e)
					{
						if($this->getUseCaching())
						{
							try
							{
								$classNamePartPrevStr = $this->getClassNameCacheStr();
								$Object = clone $this->getVarsCache($classNamePartPrevStr);
							}
							catch(Bike_Js_Exception $e)
							{
								$Object = $this;
							}										
						}
						else
						{
							$Object = $this;
						}
						
						if(substr($methodName, 0, 1) == '_')
						{
							if(method_exists($Object->_last(), $methodName))
							{
								/*$last = clone $Object->_last();
								$result = call_user_func_array(array($last, $methodName), $args);
								$Object = clone $Object;
								$Object->_unsetLast()->addName($last);
								return $Object;*/
								
								
								
								$result = call_user_func_array(array($Object->_last(), $methodName), $args);
								return $Object;
							}
							elseif($Object->_last() instanceof Bike_Js_VarPart_Setting && in_array(get_class($Object->_last()), array('Bike_Js_VarPart_Setting', 'Bike_Js_VarPart_Method')))
							{
								try
								{
									return call_user_func_array(array($this->js()->Expression($Object), $methodName), $args);
								}
								catch(Bike_Js_Exception $e)
								{
								}
							}
							elseif(!$Object->_last() instanceof Bike_Js_VarPart_Setting)
							{
								try
								{
									return call_user_func_array(array($this->js()->Expression(), $methodName), $args);
								}
								catch(Bike_Js_Exception $e)
								{
								}
							}
						}
						$Object->addMethod($methodName, $args);
						return $Object;
					}
			}
			return $this;
		}

		public function __get($Name)
		{
			if($this->getUseCaching())
			{
				$classNamePartStr = $this->getClassNameCacheStr($Name);
				try
				{				
					return $this->getVarsCache($classNamePartStr);
				}
				catch(Bike_Js_Exception $e)
				{
					try
					{
						$classNamePartPrevStr = $this->getClassNameCacheStr();
						$Object = clone $this->getVarsCache($classNamePartPrevStr);
					}
					catch(Bike_Js_Exception $e)
					{
						$Object = $this;
					}
					$Object->addMethod($Name, $this->getSettingVarTypeAttr());
					if($this->getVarCaching())
					{
						$this->setVarsCache($classNamePartStr, $Object);
					}
					return $Object;
				}			
			}
			else
			{
				$Object = $this;
				$Object->addMethod($Name, $this->getSettingVarTypeAttr());
				return $Object;
			}
		}

		protected function addMethod($Name, array $Args=null, $CheckPhpClass=True)
		{
			$this->addName($this->_getNamePart($Name, $Args, $CheckPhpClass));
            return $this;
		}

		protected function _getNamePart($Name, array $Args=null, $CheckPhpClass=True)
		{
			switch($Name)
			{
				case 'flatif':
				case 'flatIf': 		$varPartType = 'FlatIf'; break;
				case 'elseIf':
				case 'elseif': 		$Name = 'else if';
				case 'if':
				case 'while':
				case 'catch':
				case 'for':				case 'function': 	$varPartType = 'Func'; break;
				case 'try':
				case 'else': 		$varPartType = 'Structure'; break;
				case 'doWhile':
				case 'dowhile': 	$Name = 'do'; $varPartType = 'DoWhile'; break;
				case 'throw':
				case 'break':
				case 'continue':
				case 'new':
				case 'return': 		$varPartType = 'Expression'; break;
				default:            $varPartType = 'Method';
									if(sizeof($Args) == 1 && isset($Args['varPartType']))
									{
										$varPartType = $Args['varPartType'];
										$Args = array();
									}
			}
			
			try
			{
				if($CheckPhpClass)
				{					
					$classNamePartStr = $this->getClassNamePartStr($Name);
					
					$args = $Args;
					array_unshift($args, $Name);
					array_unshift($args, $this);
					
					$Object = $this->js()->getClonedObjectInstance($classNamePartStr)->setArgsOptions($args);
					if(!$Object instanceof Bike_Js_VarPart)
					{
						require_once 'Bike/Exception.php';
						throw new Bike_Exception(get_class($Object).' is not instance of Bike_Js_VarPart');
					}
					return $Object;
				}
				else
				{					require_once 'Bike/Js/Exception.php';
					throw new Bike_Js_Exception('No check PHP class');
				}
			}
			catch(Bike_Js_Exception $e)
			{
				
				array_unshift($Args, $Name);
				array_unshift($Args, $Name);
            	array_unshift($Args, $this);
            	
            	$Object = $this->js()->getClonedObjectInstance('VarPart_'.$varPartType)->setArgsOptions($Args);
            	
            	return $Object;
            }
		}

		protected function getClassNameFromName()
		{			return $this->getFullName();
		}

		public function _last()
		{			$Name = $this->getName();
			return end($Name);
		}
		
		public function _unsetLast()
		{
			$Name = $this->getName();
			array_pop($Name);
			$this->setName($Name);
			return $this;
		}

		public function _first()
		{
			$Name = $this->getName();
			return reset($Name);
		}

		public function _method($Name, $Args=array())
		{
			if($this->getUseCaching())
			{
				try
				{
					$classNamePartPrevStr = $this->getClassNameCacheStr();
					$Object = clone $this->getVarsCache($classNamePartPrevStr);
				}
				catch(Bike_Js_Exception $e)
				{
					$Object = $this;
				}
			}
			else
			{
				$Object = $this;
			}
			return $Object->addMethod($Name, $Args);
		}

		public function _setting($Name)
		{
			return $this->__get($Name);
		}

		public function offsetGet($Offset)
		{
			$methodName = '_offset';
			$last = $this->_last();
			
			$cloneFlag = False;
			
			if(sizeof($last->getOffset()))
			{
				$Object = $this;
			}
			else
			{
				$Object = clone $this;
				$cloneFlag = True;
			}
			
			if(method_exists($Object->_last(), $methodName))
			{
				if($cloneFlag)
				{
					$last = clone $last;
				}
				$last = call_user_func(array($last, $methodName), $Offset);
				$Object->_unsetLast()->addName($last);
				return $Object;
			}
			require_once 'Bike/Js/Exception.php';
			throw new Bike_Js_Exception('Method '.__METHOD__.' is not allowed');
		}

		public function offsetUnset($Offset)
		{
			require_once 'Bike/Js/Exception.php';
			throw new Bike_Js_Exception('Method '.__METHOD__.' is not allowed');
		}

		public function offsetExists($Offset)
		{
			require_once 'Bike/Js/Exception.php';
			throw new Bike_Js_Exception('Method '.__METHOD__.' is not allowed');
		}

		public function offsetSet($Offset, $Value)
		{
			require_once 'Bike/Js/Exception.php';
			throw new Bike_Js_Exception('Method '.__METHOD__.' is not allowed');
		}
		
		private function cloneNames()
		{
			foreach($this->_name as $n => $obj)
			{
				if(!$obj->getVarCaching())
				{
					$this->_name[$n] = clone $obj;
				}
			}
			return $this;
		}
		
		public function __clone()
		{
			parent::__clone();
			$this->cloneNames();			
		}
	}
